/* inxml.c is part of VIP program
 * Written by J. Salvador Arias, 2010
 *      CONICET, INSUE, Instituto Miguel Lillo,
 *      Miguel Lillo 205, S.M. de Tucuman (4000)
 *      Tucuman, Argentina
 *      e-mail: jsalarias@csnat.unt.edu.ar
 *
 * VIP is a free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation and
 * Creative Commons.
 *
 * This file and the VIP program is distributed in the hope
 * that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * To view a copy of this license, visit
 * http://creativecommons.org/licenses/GPL/2.0/
 * or send a letter to
 * Creative Commons, 171 Second Street, Suite 300,
 * San Francisco, California, 94105, USA.
 */

#include "inxml.h"

static void SkipComment (INSTREAM* in);
static void SkipCData (INSTREAM* in);

static void SkipUnicodeBOM (INSTREAM* in);

/* Check XML file */
SFLAG IsXMLFile (INSTREAM* in) {
 gchar c;
 SFLAG state;
 SFLAG tokState;
 GString* tempString;

 SkipUnicodeBOM (in);
 SkipSpaces (in);
 if (in -> nextChar != '<') return FALSE;
 ++ in -> actPos;
 in -> nextChar = in -> stream [in -> actPos];
 if (in -> nextChar != '?') {
   tempString = g_string_new (VOIDSTRING);
   in -> actPos = 0;
   in -> line = 0;
   SkipSpaces (in);
   ReadToken (in, tempString, &tokState);
   state = FALSE;
   if ((tokState & XML_TOKEN_OPEN) &&
       ((!g_strcmp0 (tempString -> str, "document")) || (!g_strcmp0 (tempString -> str, "document"))))
     state = TRUE;
   in -> actPos = 0;
   in -> line = 0;
   SkipSpaces (in);
   g_string_free (tempString, TRUE);
   return state;
 }
 ++ in -> actPos;
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (c == '>') break;
 }
 if (in -> actPos == in -> len) {
   in -> line = 0;
   in -> actPos = 0;
   SkipSpaces (in);
   return FALSE;
 }
 ++ in -> actPos;
 SkipSpaces (in);
 return TRUE;
}

/* Read a XML token */
SFLAG ReadToken (INSTREAM* in, GString* dest, SFLAG* tokenState) {
 gchar c;

 *tokenState = XML_ERROR;
 if (dest != NULL)
   g_string_erase (dest, 0, -1);
 if (in -> state == EOF_REACHED) return in -> state;
 if (in -> nextChar != '<') return in -> state;
 ++ in -> actPos;
 in -> nextChar = in -> stream [in -> actPos];
 if (in -> nextChar == '!') {
   ++ in -> actPos;
   in -> nextChar = in -> stream [in -> actPos];
   SkipComment (in);
   *tokenState = XML_TOKEN_COMMENT;
   return in -> state;
 }
 else if (in -> nextChar == '/') {
   ++ in -> actPos;
   *tokenState = XML_TOKEN_CLOSE;
 }
 else
   *tokenState = XML_TOKEN_OPEN;
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (c == '>') break;
   if (c == '/') {
      SkipParams (in);
      *tokenState = XML_TOKEN_EMPTY;
      return in -> state;
   }
   if (g_ascii_isspace (c)) {
     if (*tokenState == XML_TOKEN_CLOSE) {
       SkipParams (in);
       return in -> state;
     }
     else
       *tokenState |= XML_WITH_PARAMS;
     break;
   }
   if (dest != NULL) {
     if (g_ascii_isupper (c))
       c += 32;
     g_string_append_c (dest, c);
   }
 }
 /* Put a NULL character in the end */
 if ((dest != NULL) && (dest -> len > 0))
   g_string_append_c (dest, 0);
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
   *tokenState = XML_ERROR;
 }
 else {
   ++ in -> actPos;
   SkipSpaces (in);
 }
 return in -> state;
}

/* Read an attribute and its value */
SFLAG ReadXMLTokenParam (INSTREAM* in, GString* paramName, GString* paramVal, SFLAG* tokenState) {
 gchar c;

 *tokenState = XML_ERROR;
 if (paramName != NULL)
   g_string_erase (paramName, 0, -1);
 if (in -> nextChar == '/') {
   ++ in -> actPos;
   in -> nextChar = in -> stream [in -> actPos];
   if (in -> nextChar == '>') {
     *tokenState = XML_AUTO_CLOSE;
     ++ in -> actPos;
     SkipSpaces (in);
   }
   return in -> state;
 }
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (g_ascii_isspace (c)) return in -> state;
   if (c == '=') break;
   if (paramName != NULL) {
     if (g_ascii_isupper (c))
       c += 32;
     g_string_append_c (paramName, c);
   }
 }
 if ((paramName != NULL) && (paramName -> len > 0))
   g_string_append_c (paramName, 0);
 ++ in -> actPos;
 in -> nextChar = in -> stream [in -> actPos];
 if (in -> nextChar == '\"') {
   ++ in -> actPos;
   in -> nextChar = in -> stream [in -> actPos];
 }
 else return in -> state;
 if (paramVal != NULL)
   g_string_erase (paramVal, 0, -1);
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (c == '\"') break;
   if (paramVal != NULL)
     g_string_append_c (paramVal, c);
 }
 if ((paramVal != NULL) && (paramVal -> len > 0))
   g_string_append_c (paramVal, 0);
 ++ in -> actPos;
 in -> nextChar = in -> stream [in -> actPos];
 if (g_ascii_isspace (in -> nextChar)) {
   *tokenState = XML_WITH_PARAMS;
   SkipSpaces (in);
 }
 else if (in -> nextChar == '>') {
   *tokenState = XML_NO_PARAMS;
   ++ in -> actPos;
   SkipSpaces (in);
 }
 return in -> state;
}

/* Read XML Content */
SFLAG ReadXMLContent (INSTREAM* in, GString* dest, SFLAG* textState) {
 gchar c;

 *textState = XML_ERROR;
 if (dest != NULL)
   g_string_erase (dest, 0, -1);
 if (in -> state == EOF_REACHED) return in -> state;
 if (in -> state == EOF_REACHED) return in -> state;
 if (in -> nextChar == '<') {
   *textState = XML_TOKEN_NOCONTENT;
   return in -> state;
 }
 if (in -> nextChar == '!') {
   ++ in -> actPos;
   in -> nextChar = in -> stream [in -> actPos];
   if (in -> nextChar == '[') {
     SkipCData (in);
     *textState = XML_TOKEN_CDATA;
     return in -> state;
   }
 }
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (c == '<') {
     in -> nextChar = '<';
     break;
   }
   if ((dest != NULL) && (c != '\r') && (c != '&'))
     g_string_append_c (dest, c);
   else if ((dest != NULL) && (c == '&'))
     g_string_append (dest, "and");
 }
 /* Put a NULL character in the end */
 if ((dest != NULL) && (dest -> len > 0))
   g_string_append_c (dest, 0);
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
 }
 else {
   SkipSpaces (in);
    *textState = XML_TOKEN_CONTENT;
 }
 return in -> state;
}

void SkipComment (INSTREAM* in) {
 gchar c, prevC, farC;
 SFLAG cdataFlag;

 prevC = '!';
 farC = '<';
 cdataFlag = FALSE;
 if (in -> stream [in -> actPos] == '[')
   cdataFlag = TRUE;
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if (c == '>') {
     if ((cdataFlag == TRUE) && (prevC == ']') && (farC == ']')) break;
     if ((cdataFlag == FALSE) && (prevC == '-') && (farC == '-')) break;
   }
   farC = prevC;
   prevC = c;
 }
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
 }
 else {
   ++ in -> actPos;
  SkipSpaces (in);
 }
}

void SkipCData (INSTREAM* in) {
 gchar c, prevC, farC;

 farC = '!';
 prevC = '[';
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   c = in -> stream [in -> actPos];
   if ((c == '<') && (prevC == ']') && (farC == ']')) break;
   farC = prevC;
   prevC = c;
 }
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
 }
 else {
   ++ in -> actPos;
  SkipSpaces (in);
 }
}

SFLAG SkipParams (INSTREAM* in) {
 gchar c, prevC;

 prevC = c = '<';
 for (; in -> actPos < in -> len; ++ in -> actPos) {
   prevC = c;
   c = in -> stream [in -> actPos];
   if (c == '>') break;
 }
 if (in -> actPos == in -> len) {
   in -> nextChar = 0;
   in -> state = EOF_REACHED;
   return XML_ERROR;
 }
 else {
   ++ in -> actPos;
  SkipSpaces (in);
 }
 if (prevC == '/')
   return XML_AUTO_CLOSE;
 return XML_NO_PARAMS;
}

SFLAG ReadXMLFile (XMLPARSER* xmlParser) {
 GString* actToken;
 SFLAG toRet;

 toRet = TRUE;
 for (;;) {
   ReadToken (xmlParser -> in, xmlParser -> tempParserString, &xmlParser -> tokenState);
   if (xmlParser -> tokenState == XML_ERROR) {
     errorMessage = g_strdup_printf ("\nUnexpected file format\nFile %s, line %ld\n", xmlParser -> in -> name, xmlParser -> in -> line);
     if (xmlParser -> OnError != NULL)
       toRet = xmlParser -> OnError ();
     else {
       OnError ();
       toRet = FALSE;
     }
     break;
   }
   if (xmlParser -> tokenState & XML_TOKEN_CLOSE) {
     if (xmlParser -> tokenStack == NULL) {
       errorMessage = g_strdup_printf ("\nUnexpected \'</%s>\' token\nFile %s, line %ld\n",
                                       xmlParser -> tempParserString -> str, xmlParser -> in -> name, xmlParser -> in -> line);
       if (xmlParser -> OnError != NULL)
         toRet = xmlParser -> OnError ();
       else {
         OnError ();
         toRet = FALSE;
       }
       break;
     }
     actToken = (GString*) xmlParser -> tokenStack -> data;
     if (g_strcmp0 (xmlParser -> tempParserString -> str, actToken -> str)) {
       errorMessage = g_strdup_printf ("\nReading \'</%s>\', expecting token: </%s>\nFile %s, line %ld\n",
                         xmlParser -> tempParserString -> str, actToken -> str, xmlParser -> in -> name, xmlParser -> in -> line);
       if (xmlParser -> OnError != NULL)
         toRet = xmlParser -> OnError ();
       else {
         OnError ();
         toRet = FALSE;
       }
       break;
     }
     if (!g_strcmp0 (xmlParser -> tempParserString -> str, xmlParser -> fileHeader)) break;
     if ((xmlParser -> EndTag != NULL) && (xmlParser -> EndTag () == FALSE)) {
       if (xmlParser -> OnError != NULL)
         toRet = xmlParser -> OnError ();
       else
         toRet = FALSE;
       break;
     }
     RemoveFromTokenList (xmlParser);
   }
   else if (xmlParser -> tokenState & XML_TOKEN_OPEN) {
     actToken = g_string_new (xmlParser -> tempParserString -> str);
     xmlParser -> tokenStack = g_list_prepend (xmlParser -> tokenStack, actToken);
     if (xmlParser -> OpenTag == NULL) {
       if (IgnoreOpenTag (xmlParser) == FALSE) {
         if (xmlParser -> OnError != NULL)
           toRet = xmlParser -> OnError ();
         else
           toRet = FALSE;
         break;
       }
     }
     else if (xmlParser -> OpenTag () == FALSE) {
       if (xmlParser -> OnError != NULL)
         toRet = xmlParser -> OnError ();
       else
         toRet = FALSE;
       break;
     }
   }
 }
 if (xmlParser -> fileHeader != NULL)
   g_free (xmlParser -> fileHeader);
 xmlParser -> fileHeader = NULL;
 CleanTokenStack (xmlParser -> tokenStack);
 xmlParser -> tokenStack = NULL;
 return toRet;
}

void RemoveFromTokenList (XMLPARSER* xmlParser) {
 GList* toPop;
 toPop = xmlParser -> tokenStack;
 xmlParser -> tokenStack = g_list_next (xmlParser -> tokenStack);
 g_string_free (toPop -> data, TRUE);
 DestroyLink (toPop);
}

SFLAG IgnoreOpenTag (XMLPARSER* xmlParser) {
 SFLAG state;

 if (xmlParser -> tokenState & XML_WITH_PARAMS) {
   state = SkipParams (xmlParser -> in);
   if (state == XML_AUTO_CLOSE) {
     RemoveFromTokenList (xmlParser);
     return TRUE;
   }
   else if (state == XML_ERROR) return FALSE;
 }
 state = ReadXMLContent (xmlParser -> in, NULL, &xmlParser -> tokenState);
 if (state == XML_ERROR) return FALSE;
 return TRUE;
}

GString* GetPrevToken (XMLPARSER* xmlParser) {
 GList* prevToken;

 if (xmlParser -> tokenStack == NULL) return NULL;
 prevToken = g_list_next (xmlParser -> tokenStack);
 if (prevToken == NULL) return NULL;
 return  prevToken -> data;
}

void CleanTokenStack (GList* toClean) {
 GList* nav;
 for (nav = toClean; nav != NULL; nav = g_list_next (nav))
   g_string_free (nav -> data, TRUE);
 g_list_free (toClean);
}

void SkipUnicodeBOM (INSTREAM* in) {
 gchar c;

 c = in -> stream [in -> actPos];
 if (c == -17) {
   in -> actPos += 3;
   in -> nextChar = in -> stream [in -> actPos];
 }
}
